/* interface/hialarm/hialarm.c
 *
 * Copyright (c) 2006 Hisilicon Co., Ltd.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 *
 * History:
 *      16-12-2006 Start of Hi3560 CPU support
 */


#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/sched.h>
//#include <asm/hardware.h>
#include <linux/interrupt.h>

#include <linux/poll.h>
#include <linux/miscdevice.h>
#include <linux/errno.h>
#include <linux/fcntl.h>

#include <linux/init.h>
#include <linux/delay.h>
#include <linux/proc_fs.h>
#include <linux/workqueue.h>

#include <asm/uaccess.h>
#include <asm/system.h>
#include <asm/io.h>
#include <linux/version.h>

#include "hi_alarm.h"

#define HIALARM_DEVICE_IRQ_NO   31

#define HI_MUXCTL_BASE  (IO_ADDRESS(0x200F0000))
#define HI_MUXCTL_REG47 (0x0BC) //GPIO5_2 /PWM_OUT0
#define HI_MUXCTL_REG56 (0x0E0) //GPIO9_6 


#define HI_GPIO5_BASE (IO_ADDRESS(0x20190000))
#define HI_GPIO9_BASE (IO_ADDRESS(0x201D0000))

#define GPIO_DAT(base, x) (base + (1 << (2 + (x))))
#define GPIO_DIR    (0x400)
#define GPIO_IS     (0x404)
#define GPIO_IBE    (0x408)
#define GPIO_IEV    (0x40c)
#define GPIO_IE     (0x410)
#define GPIO_RIS    (0x414)
#define GPIO_MIS    (0x418)
#define GPIO_IC     (0x41c)


/* define macro */
#define WRITE_REG(Addr, Value) ((*(volatile unsigned int *)(Addr)) = (Value))
#define READ_REG(Addr)         (*(volatile unsigned int *)(Addr))

/* debug */
static int g_dbg_flag = 0;
#define HIALARM_PFX "hialarm: "
#define hialarm_dbg(params...) \
    do{ \
        if(g_dbg_flag) \
        { \
            printk(KERN_DEBUG HIALARM_PFX params); \
        } \
    }while(0);



static void hialarm_config( void )
{
    int iev,dirv;
    
    /* TODO: ASIC config pin share */
    WRITE_REG(HI_MUXCTL_BASE+HI_MUXCTL_REG47, 0); //set gpio5_2 as gpio
    WRITE_REG(HI_MUXCTL_BASE+HI_MUXCTL_REG56, 1); //set gpio9_6 as gpio

    WRITE_REG(GPIO_DAT(HI_GPIO5_BASE, 2), 0x00);
    dirv = READ_REG(HI_GPIO5_BASE + GPIO_DIR);
    WRITE_REG(HI_GPIO5_BASE + GPIO_DIR,dirv|0x04);  //set gpio5_2 as output

    dirv = READ_REG(HI_GPIO9_BASE + GPIO_DIR);
    WRITE_REG(HI_GPIO9_BASE + GPIO_DIR,dirv&0xBF);  //set gpio9_6 as input

    dirv = READ_REG(HI_GPIO9_BASE + GPIO_IS);
    WRITE_REG(HI_GPIO9_BASE + GPIO_IS,dirv&0xBF);  //set gpio9_6 as edge troggle
    
    dirv = READ_REG(HI_GPIO9_BASE + GPIO_IBE);
    WRITE_REG(HI_GPIO9_BASE + GPIO_IBE,dirv&0xBF);  //set gpio9_6 as single edge troggle

    dirv = READ_REG(HI_GPIO9_BASE + GPIO_IBE);
    WRITE_REG(HI_GPIO9_BASE + GPIO_IBE,dirv&0xBF);  //set gpio9_6 as single edge troggle

    dirv = READ_REG(HI_GPIO9_BASE + GPIO_IEV);
    WRITE_REG(HI_GPIO9_BASE + GPIO_IEV,dirv&(1 << 6));  //set gpio9_6 as single raise edge troggle
    
    iev = READ_REG(HI_GPIO5_BASE + GPIO_IE);
    WRITE_REG(HI_GPIO5_BASE + GPIO_IE, iev&0xFB);   //disable gpio5_2 interrupt

    iev = READ_REG(HI_GPIO9_BASE + GPIO_IE);
    WRITE_REG(HI_GPIO9_BASE + GPIO_IE, iev|(0x1 << 6));   //enable gpio9_6 interrupt
}

static irqreturn_t hialarm_interrupt( int irq, void *dev_id )
{
    int v;

    v = READ_REG(HI_GPIO9_BASE + GPIO_IE);
    WRITE_REG(HI_GPIO9_BASE + GPIO_IE, v & 0xBF);   //disable gpio9_6 interrupt

    v = READ_REG(HI_GPIO9_BASE + GPIO_IC);
    WRITE_REG(HI_GPIO9_BASE + GPIO_IC, v|(0x1 << 6));   // clear interrupt
    
    printk("hialarm_interrupt\n");
    WRITE_REG(GPIO_DAT(HI_GPIO5_BASE, 2), 0x00);    //close alarm

    v = READ_REG(HI_GPIO9_BASE + GPIO_IE);
    WRITE_REG(HI_GPIO9_BASE + GPIO_IE, v|(0x1 << 6));   //enable gpio9_6 interrupt
    return IRQ_HANDLED;
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,36)
static int hialarm_ioctl( struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg )
#else
static long hialarm_ioctl( struct file *filp, unsigned int cmd, unsigned long arg )
#endif
{
    //int __user *p = ( int __user * )arg;

    switch ( cmd )
    {
        case GPIO_IOC_DIR:
            break;
            
        case GPIO_IOC_PORT:
            break;
            
        case GPIO_IOC_DATA:
            if (arg)
            {
                WRITE_REG(GPIO_DAT(HI_GPIO5_BASE, 2), 0x04);
            }
            else
            {
                WRITE_REG(GPIO_DAT(HI_GPIO5_BASE, 2), 0x00);
            }
            break;
        default:
            printk( KERN_DEBUG HIALARM_PFX "Error: Inappropriate ioctl for device. cmd=%d\n", cmd );
            return -ENOTTY;
    }

    return 0;
}

static ssize_t hialarm_read( struct file *filp, char __user *buf, size_t count, loff_t *f_pos )
{
    int res = 0;
    char c;

    if ( buf == NULL )
    {
        return -1;
    }
    
    c = READ_REG(GPIO_DAT(HI_GPIO5_BASE, 2));
    res = copy_to_user( buf, &c, sizeof(c) );
    return res;
}

#if 0
static unsigned int hialarm_select( struct file *filp, struct poll_table_struct *wait )
{
    hialarm_dbg( "Enter hiir_select.\n" );
    if ( ( hiir_dev.head ) != ( hiir_dev.tail ) )
    {
        return 1;
    }
    poll_wait( filp, &( hiir_dev.irkey_wait ), wait );
    return 0;
}
#endif

static atomic_t hialarm_s_available = ATOMIC_INIT( 1 );

static int hialarm_open( struct inode *inode, struct file *filp )
{
    if ( ! atomic_dec_and_test( &hialarm_s_available ) )
    {
        atomic_inc( &hialarm_s_available );
        printk( KERN_DEBUG HIALARM_PFX "Error: device already open.\n" );
        return -EBUSY;
    }

    hialarm_config();
    return 0;
}

static int hialarm_release( struct inode *inode, struct file *filp )
{
    WRITE_REG(GPIO_DAT(HI_GPIO5_BASE, 2), 0x00);
    atomic_inc( &hialarm_s_available );

    return 0;
}

static int hialarm_write( struct file *filp, const char __user *buf, size_t count, loff_t *f_pos )
{
    return 0;
}

static struct file_operations hialarm_fops =
{
owner   :
    THIS_MODULE,
open    :
    hialarm_open,
unlocked_ioctl   :
    hialarm_ioctl,
poll    :
    NULL,//hialarm_select,
read    :
    hialarm_read,
write   :
    hialarm_write,
release :
    hialarm_release,
};

static struct miscdevice hialarm_miscdev =
{
    .minor = MISC_DYNAMIC_MINOR,
    .name  = HIALARM_DEVICE_NAME,
    .fops  = &hialarm_fops,
};

static int __init hialarm_init( void )
{
    int res = 0;

    printk( KERN_INFO OSDRV_MODULE_VERSION_STRING "\n" );


    res = misc_register( &hialarm_miscdev );
    if ( 0 != res )
    {
        printk( KERN_DEBUG HIALARM_PFX "Error: can't register\n" );
        return -EFAULT;
    }

    res = request_irq( HIALARM_DEVICE_IRQ_NO, hialarm_interrupt, 0, HIALARM_DEVICE_NAME, &hialarm_interrupt );
    if ( res )
    {
        printk( KERN_DEBUG HIALARM_PFX "Error: request IRQ(%d) failed\n", HIALARM_DEVICE_IRQ_NO );
        misc_deregister( &hialarm_miscdev );
        goto hi_alarm_init_failed;
    }
    
    printk( HIALARM_PFX "init ok. ver=%s, %s.\n", __DATE__, __TIME__ );
hi_alarm_init_failed:
    return res;
}

static void __exit hialarm_exit( void )
{
    free_irq( HIALARM_DEVICE_IRQ_NO, &hialarm_interrupt );
    misc_deregister( &hialarm_miscdev );
}

module_init( hialarm_init );
module_exit( hialarm_exit );
MODULE_LICENSE( "GPL" );
MODULE_DESCRIPTION( "Hisilicon GPIO Alarm Device Driver" );
MODULE_VERSION( "HI_VERSION=" OSDRV_MODULE_VERSION_STRING );

